#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <libgen.h>
#include <getopt.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>

/*Macros*/
/*App's version*/
#define _VERSION	"1.0.0"
/*Default UDP remote port number*/
#define DEFAULT_PORT "1111"
/*Default remote ip address*/
#define DEFAULT_ADDR "255.255.255.255"	
/*Path to file containing knonw devices*/
#define DEVICES_FILE "/etc/wol.conf"

/* Typedefs*/
typedef uint8_t mac_addr[6];

/* Constants*/
const char * appname;
int VERBOSE = 0 ;


void help(void)
{
printf("\
WOL - Wake On Lan\n\
Author    : Pako <pawel.lebioda89@gmail.com>\n\
Version   : %s\n\
Build date: %s %s\n\
\n",_VERSION,__DATE__,__TIME__);
printf("\
Usage: %s [OPTIONS] [MAC|NAME]\
\n\n",appname);
printf("\
Available options:\
\n");
printf("\
-h           print this help\
\n");
printf("\
-p <number>  UDP port number\n\
             [default: %s]\
\n",DEFAULT_PORT);
printf("\
-a <ip>      IP address\n\
             [default: %s]\
\n",DEFAULT_ADDR);
printf("\
-v           increment verbose level\
\n");
printf("\
-l           list all known devices\
\n");
}


int scan_mac(const char * cmac,mac_addr * mac)
{
	return sscanf(cmac,"%x:%x:%x:%x:%x:%x",&(*mac)[0],&(*mac)[1],&(*mac)[2],&(*mac)[3],&(*mac)[4],&(*mac)[5]);
}

#define DEVICE_NAME_SIZE	255
#define DEVICE_ADDR_SIZE	20
void get_mac_addr(const char * cmac,mac_addr * mac)
{	
	FILE * fh;
	char device_name[DEVICE_NAME_SIZE];
	char device_addr[DEVICE_ADDR_SIZE];
	if(strlen(cmac) < 11 || scan_mac(cmac,mac) != 6)
	{
		fh = fopen(DEVICES_FILE,"r");
		if(fh==NULL)
			fprintf(stderr,"%s: create %s file with knonw devices\n",appname,DEVICES_FILE);
		else
		{
			while(fscanf(fh,"%s %s",device_name,device_addr) == 2)
			{
				if(device_name[0] == '#')
					continue;
				if(!strcmp(device_name,cmac))
				{
					if(scan_mac(device_addr,mac) == 6)
					{
						fclose(fh);
						return;
					}
				}
			}
			fclose(fh);
		}
		fprintf(stderr,"%s: wrong MAC address format\n",cmac);
		exit(EXIT_FAILURE);
	}
}
/* 6 bytes of 0xff and 16 times MAC addr*/
#define BUFF_SIZE	(6 + 16*6)

void send_wol(const char * cmac,const char * caddr,const char * cport)
{
	int sock;
	struct sockaddr_in addr_dst;
	int port;
	uint8_t buff[BUFF_SIZE],*ptr,i=16;
	mac_addr mac;
	memset(buff,0xff,6);
	ptr = buff + 6;
	get_mac_addr(cmac,&mac);
	while(i--)
	{
		memcpy(ptr,&mac,sizeof(mac));
		ptr += sizeof(mac);
	}
	port = atoi(cport);
	if(port < 0 || (port == 0 && cport[0] != '0'))
	{
		fprintf(stderr,"%s: wrong port number\n",cport);
		exit(EXIT_FAILURE);
	}
	if((sock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP))<0)
	{
		perror("socket");
		exit(EXIT_FAILURE);
	}
	int broadcast = 1;
	if(setsockopt(sock,SOL_SOCKET,SO_BROADCAST,&broadcast,sizeof(broadcast)) < 0)
	{
		perror("setsockopt");
		exit(EXIT_FAILURE);
	}
	memset(&addr_dst,0,sizeof(addr_dst));
	addr_dst.sin_family = AF_INET;
	addr_dst.sin_port = htons(port);
	if(caddr)
		addr_dst.sin_addr.s_addr = inet_addr(caddr);
	else
		addr_dst.sin_addr.s_addr = htonl(INADDR_BROADCAST);
	if(sendto(sock,buff,BUFF_SIZE,0,(struct sockaddr*)&addr_dst,sizeof(addr_dst)) < 0 )
	{
		perror("sendto");
		exit(EXIT_FAILURE);
	}
	close(sock);
	if(VERBOSE > 0)
	{
		printf("Sending Wake On Lan Magic Packet...\n");
		printf("Mac : %02x:%02x:%02x:%02x:%02x:%02x\n",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
		printf("Port: %s\n",cport);
		printf("Addr: %s\n",caddr); 
	}
}

#define WOL_SYNC_LEN	6
#define WOL_MAC_REP	16
int wake_on_lan(int sock,struct sockaddr_in * dst,mac_addr * mac)
{
	uint8_t buff[BUFF_SIZE],*ptr;
	int i,ret;
	memset(buff,0xff,WOL_SYNC_LEN);
	ptr = buff+WOL_SYNC_LEN;
	for(i=0;i<WOL_MAC_REP;i++)
	{
		memcpy(ptr,mac,sizeof(*mac));
		ptr += sizeof(*mac);
	}
	if(sendto(sock,buff,BUFF_SIZE,0,(struct sockaddr*)dst,sizeof(*dst))<0)
	{
		perror("sendto");
		return -1;
	}
	return 0;
}

void list_devices(void)
{
	printf("List of knonw devices [from %s]:\n",DEVICES_FILE);
	execlp("/bin/cat","/bin/cat",DEVICES_FILE,NULL);
	perror("cat");
	exit(EXIT_FAILURE);
}

int main(int argc,char * argv[])
{
	appname = basename(argv[0]);
	const char * optstr = "hvlp:a:";
	char opt;
	const char * caddr = DEFAULT_ADDR;
	const char * cport = DEFAULT_PORT;
	mac_addr mac;
	int sock,tmp,port,ret;
	struct sockaddr_in addr_dst;
	while((opt=getopt(argc,argv,optstr)) != -1)
	{
		switch(opt)
		{
			case 'h':
			help();
			exit(EXIT_SUCCESS);
			case 'p':
			cport = optarg;
			break;
			case 'a':
			caddr = optarg;
			break;
			case 'v':
			VERBOSE++;
			break;
			case 'l':
			list_devices();
			case '?':	
			exit(EXIT_FAILURE);
		}
	}
	if(optind != argc-1)
	{
		help();
		exit(EXIT_FAILURE);
	}
	/*Get mac address*/
	get_mac_addr(argv[optind],&mac);
	/*Get port number*/
	port = atoi(cport);
	if(port < 0 || (port == 0 && cport[0] != '0'))
	{
		fprintf(stderr,"%s: wrong port number -- '%s'\n",appname,cport);
		exit(EXIT_FAILURE);
	}
	/* Create socket*/
	if((sock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP))<0)
	{
		perror("socket");
		exit(EXIT_FAILURE);
	}
	tmp=1; /*Broadcast socket*/
	if((ret=setsockopt(sock,SOL_SOCKET,SO_BROADCAST,&tmp,sizeof(tmp))) < 0)
	{
		perror("setsockopt");
		exit(EXIT_FAILURE);
	}
	/*Set remote address*/
	memset(&addr_dst,0,sizeof(addr_dst));
	addr_dst.sin_family = AF_INET;
	addr_dst.sin_port = htons(port);
	addr_dst.sin_addr.s_addr = inet_addr(caddr);
	/*Send WOL Magic Packet*/
	if(wake_on_lan(sock,&addr_dst,&mac))
	{
		fprintf(stderr,"%s: sending WOL Magic Packet failed!\n",appname);
		exit(EXIT_FAILURE);
	}
	/*Close socket*/
	close(sock);
	/*Print messages*/
	if(VERBOSE > 0)
	{
		printf("Sending Wake On Lan Magic Packet...\n");
		printf("Mac : %02x:%02x:%02x:%02x:%02x:%02x\n",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
		printf("Port: %s\n",cport);
		printf("Addr: %s\n",caddr); 
	}
	exit(EXIT_SUCCESS);
}
